package com.mojavelinux.open18;

import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;

import javax.faces.application.FacesMessage;

import org.appfuse.service.GenericManager;
import org.jboss.seam.ScopeType;
import org.jboss.seam.annotations.In;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.Out;
import org.jboss.seam.annotations.Scope;
import org.jboss.seam.core.Events;
import org.jboss.seam.core.FacesMessages;

@Name("courseAction")
@Scope(ScopeType.CONVERSATION)
public class CourseAction implements Serializable {

	private static final long serialVersionUID = 1L;

	@In("#{courseManager}")
	private GenericManager<Course, Long> courseManager;
	
	@In( create = true )
	private CourseFactory courseFactory;
	
	@In
	private FacesMessages facesMessages;
	
	// Allow either an inject via data model selection, or allow the course
	// to be created via the factory method as part of a page action.
	@In(required = false)
	@Out(required = false)
	private Course course;
	
	@In(required = false)
	private Long courseId;
	
	@In(value="_course", required = false)
	private Course selectedCourse;
	
	private String color;

	public String select( Course course ) {
		this.selectedCourse = course;
		return select();
	}
	
	// A selection action from the UI.  Indicates that a navigation event is required.
	public String select() {
		loadCourse();
		return "detail";
	}
	
	public String edit( Course course ) {
		this.selectedCourse = course;
		return edit();
	}
	
	public String edit() {
		loadCourse();
		if ( course.isScorecardEmpty() ) {
			courseFactory.initializeScorecard( course );
		}
		
		courseFactory.initializeRatings( course.getTeeSets() );
		
		return "editor";
	}
	
	/**
	 * Validate and save the course data.  At a minimum a men's par must be specified for
	 * either 9 or 18 sequential holes.  If ladies information is not provided, it is copied
	 * from the men's information.  Handicap information is not provided.  For every tee set,
	 * if at least one distance is provided, then distance information must be present for
	 * all tees (holes).  If validation is successful, tees are removed from tee sets that
	 * did not provide any data.  If the back nine was not specified, then the back nine holes
	 * are cleared from the course.
	 */
	public String save() {
		boolean incomplete = false;
		boolean hasIncompleteHoleFront = false;
		boolean hasIncompleteHoleBack = false;
		boolean hasBack = false;
		for ( Hole hole : course.getHoles() ) {
			// mark if at least one back 9 hole has been specified with data
			if ( hole.getNumber() > 9 && hole.getMensPar() != null ) {
				hasBack = true;
			}
			
			if ( hole.getMensPar() == null ) {
				if ( hole.getNumber() <= 9 ) {
					hasIncompleteHoleFront = true;
				}
				else {
					hasIncompleteHoleBack = true;
				}
			}
			else {
				// if not set, make it the same as the mens
				if ( hole.getLadiesPar() == null ) {
					hole.setLadiesPar( hole.getMensPar() );
				}
			}
			
			if ( hole.getMensHandicap() != null && hole.getLadiesHandicap() == null ) {
				hole.setLadiesHandicap( hole.getMensHandicap() );
			}
		}
		
		if ( hasIncompleteHoleFront || ( hasBack && hasIncompleteHoleBack ) ) {
			facesMessages.add( FacesMessage.SEVERITY_ERROR, "The course must have a men's par for every hole (9 or 18)." );
			incomplete = true;
		}
		
		Set<TeeSet> blankTeeSets = new HashSet<TeeSet>();
		
		for ( TeeSet teeSet : course.getTeeSets() ) {
			int holesWithData = 0;
			for ( Tee tee : teeSet.getTees() ) {
				if ( tee.getDistanceToGreen() != null ) {
					holesWithData++;
				}
			}
			
			if ( holesWithData == 0 ) {
				blankTeeSets.add( teeSet );
			}
			else if ( holesWithData < 18 ) {
				facesMessages.add( FacesMessage.SEVERITY_ERROR, "The " + teeSet.getColor() + " tee set has missing distance data." );
				incomplete = true;
			}
		}
		
		if ( incomplete ) {
			return "failure";
		}
		
		for( TeeSet teeSet : course.getTeeSets() ) {
			if ( blankTeeSets.contains( teeSet ) ) {
				teeSet.getTees().clear();
			}
		}
		
		// TODO: clear back holes if not provided
		if ( !hasBack ) {}
		
		// poor man's way of determining if this is a managed object
		boolean isNew = ( course.getId() == 0 );
		courseManager.save( course );
		if ( isNew ) {
			Events.instance().raiseEvent( "courseListChanged", course, "add" );
			facesMessages.add( "#{course.name} has been added to the database." );
		}
		else {
			// QUESTION: should we raise an update event?
			facesMessages.add( "Successfully updated #{course.name}." );
		}
		
		return "success";
	}
	
	public String saveAndReturn() {
		return save();
	}
	
	public String create() {
		course = courseFactory.createCourse();
		return "editor";
	}
	
	/**
	 * Consider three cases:
	 * 1. restored from conversation scope
	 * 2. selection from data model list
	 * 3. direct reference via permalink
	 */
	public String loadCourse() {
		if ( selectedCourse != null && selectedCourse.getId() > 0 ) {
			course = courseManager.get( selectedCourse.getId() );
			selectedCourse = null;
		}
		else if ( course != null && course.getId() > 0 ) {
			// no action
		}
		else if ( courseId != null ) {
			course = courseManager.get( courseId );
			courseId = null;
			return "success";
		}
		else {
			return "invalid";
		}
		
		return null;
	}
	
	public String getColor() {
		return color;
	}

	public void setColor( String color ) {
		this.color = color;
	}
	
}
